package IC.LIR;

//import java.lang.reflect.Array;
//import java.util.Arrays;

import IC.AST.*;
import IC.SymbolTable.SemanticError;



/**
 * Setti-Ulman optimization - Visitor to check the minimum requiered number of registers in each AST node
 */
public class SettiUlmanRegisterCounterVisitor implements Visitor {

	
	// the maximum number of registers in use
	public Object visit(Program program) throws SemanticError
	{
		int maxNumOfReg = 0;
		//check in every class
		for (ICClass c: program.getClasses()){
			// update maximum number of registers in use
			int classNumOfReg = (Integer) c.accept(this);
			maxNumOfReg = Math.max(classNumOfReg, maxNumOfReg);
		}
		program.setNumOfRegisters(maxNumOfReg);
		return maxNumOfReg;
	}


	// return the maximum number of registers in use
	public Object visit(ICClass icClass) throws SemanticError {
		int maxNumOfReg = 0;
		for (Method m: icClass.getMethods()){
			// update maximum number of registers in use
			int methodNumOfReg = (Integer) m.accept(this);
			maxNumOfReg = Math.max(methodNumOfReg, maxNumOfReg);
		}
		icClass.setNumOfRegisters(maxNumOfReg);
		return maxNumOfReg;
	}


	// Always 0  ******************************************
	public Object visit(Field field) {
		// always 0
		field.setNumOfRegisters(0);
		return 0;
	}
	public Object visit(LibraryMethod method) {
		//always 0
		method.setNumOfRegisters(0);
		return 0;
	}

	public Object visit(Formal formal) {
		// always 0
		formal.setNumOfRegisters(0);
		return 0;
	}

	public Object visit(PrimitiveType type) {
		// always 0
		type.setNumOfRegisters(0);
		return 0;
	}

	public Object visit(UserType type) {
		// always 0
		type.setNumOfRegisters(0);
		return 0;
	}

	public Object visit(Break breakStatement) {
		// always 0
		breakStatement.setNumOfRegisters(0);
		return 0;
	}

	public Object visit(Continue continueStatement) {
		// always 0
		continueStatement.setNumOfRegisters(0);
		return 0;
	}


	public Object visit(Literal literal) {
		// always 0
		literal.setNumOfRegisters(0);
		return 0;
	}

	//***********************************************************

	////////////////////////////////////////////////////
	public Object methodNumOfReg(Method method) throws SemanticError{
		int maxNumOfReg = 0;
		for (Statement s: method.getStatements())
		{
			int statReq = (Integer) s.accept(this);
			maxNumOfReg = Math.max(statReq, maxNumOfReg);
		}
		method.setNumOfRegisters(maxNumOfReg);
		return maxNumOfReg;
	}

	public Object visit(VirtualMethod method) throws SemanticError {
		return methodNumOfReg(method);
	}


	public Object visit(StaticMethod method) throws SemanticError {
		return methodNumOfReg(method);
	}


	////////////////////////////////////////////


	
		// This visitor
		public Object visit(This thisExpression) {
			int maxNumOfReg=0; // 1
			thisExpression.setNumOfRegisters(maxNumOfReg);
			return maxNumOfReg;
		}

	
		public Object visit(NewClass newClass) {
			int maxNumOfReg=0; // 
			newClass.setNumOfRegisters(maxNumOfReg);
			return maxNumOfReg;
		}


	// return  the maximum number of registers in use
	public Object visit(CallStatement callStatement) throws SemanticError 
	{
		int numOfReg = (Integer)callStatement.getCall().accept(this);
		callStatement.setNumOfRegisters(numOfReg);
		return numOfReg;
	}

	public Object visit(Return returnStatement) throws SemanticError
	{
		int numOfReg = (Integer)returnStatement.getValue().accept(this);
		returnStatement.setNumOfRegisters(numOfReg);
		return numOfReg;
	}


	// O.K
	public Object visit(If ifStatement) throws SemanticError
	{
		int condition=(Integer)ifStatement.getCondition().accept(this);
		int operation= (Integer)ifStatement.getOperation().accept(this);
		
		int numOfReg = Math.max(condition, operation);
		int elseNumOfRegisters=0;
		//check if ifStatement has else
		if (ifStatement.hasElse())
		{
			// calculate the number of registers of elseStatement
			elseNumOfRegisters = (Integer)ifStatement.getElseOperation().accept(this);
		}
		numOfReg = Math.max(numOfReg, elseNumOfRegisters);
		ifStatement.setNumOfRegisters(numOfReg);
		return numOfReg;
	}


	public Object visit(While whileStatement) throws SemanticError
	{
		int condition=(Integer)whileStatement.getCondition().accept(this);
		int operation=(Integer)whileStatement.getOperation().accept(this);
		
		int numOfReg = Math.max(operation, condition);
		whileStatement.setNumOfRegisters(numOfReg);
		return numOfReg;
	}



	public Object visit(StatementsBlock statementsBlock) throws SemanticError 
	{
		int maxNumOfReg = 0;
		for (Statement s: statementsBlock.getStatements())
		{
			int statNumOfRegisters = (Integer) s.accept(this);
			maxNumOfReg = Math.max(statNumOfRegisters, maxNumOfReg);
		}
		statementsBlock.setNumOfRegisters(maxNumOfReg);
		return maxNumOfReg;
	}


	//O.K
	public Object visit(LocalVariable localVariable) throws SemanticError 
	{
		int numOfReg=0;
		if (localVariable.hasInitValue())
		{
			// local variable is initialized
			numOfReg = (Integer)localVariable.getInitValue().accept(this);
		}

		localVariable.setNumOfRegisters(numOfReg);
		return numOfReg;
	}

	//O.K
	public Object visit(VariableLocation location) throws SemanticError
	{
		int numOfReg =0; // 0;
		if (location.isExternal())
		{
			numOfReg = (Integer)location.getLocation().accept(this);
		}
		location.setNumOfRegisters(numOfReg);
		return numOfReg;
	}



/// ??????????????????????????

	public Object visit(StaticCall call) throws SemanticError 
	{
		 for (Expression e : call.getArguments()){
	            e.accept(this);
		}
		
		//int numOfReg=0;
		int numOfReg = Integer.MAX_VALUE;
		call.setNumOfRegisters(numOfReg);
		
		return numOfReg;
	}

	public Object visit(VirtualCall call) throws SemanticError 
	{
		if (call.isExternal()){
			call.getLocation().accept(this);
			}
		for (Expression e : call.getArguments()){
            e.accept(this);}
		
		//int numOfReg=0;
		int numOfReg = Integer.MAX_VALUE;
		call.setNumOfRegisters(numOfReg);
		
		return numOfReg;
	}

	public Object visit(NewArray newArray) throws SemanticError 
	{
		int numOfReg = (Integer)newArray.getSize().accept(this);
		newArray.setNumOfRegisters(numOfReg);
		return numOfReg;
	}

	// Length visitor:
	public Object visit(Length length) throws SemanticError {
		int numOfReg = (Integer)length.getArray().accept(this);
		length.setNumOfRegisters(numOfReg);
		return numOfReg;
	}

	// MathUnaryOp visitor
	public Object visit(MathUnaryOp mathUnaryOp) throws SemanticError
	{
		int numOfReg = (Integer)mathUnaryOp.getOperand().accept(this);
		mathUnaryOp.setNumOfRegisters(numOfReg);
		return numOfReg;
	}


	// LogicalUnaryOp visitor	
	public Object visit(LogicalUnaryOp logicalUnaryOp) throws SemanticError 
	{
		int numOfReg = (Integer)logicalUnaryOp.getOperand().accept(this);
		logicalUnaryOp.setNumOfRegisters(numOfReg);
		return numOfReg;
	}

	public Object visit(ExpressionBlock expressionBlock) throws SemanticError {
		int numOfReg = (Integer)expressionBlock.getExpression().accept(this);
		expressionBlock.setNumOfRegisters(numOfReg);
		return numOfReg;
	}

	//******************************************
	// returns the number of registers used by the Setti-Ulman algorithm
	private static int settiUlmanAlg(int num1, int num2){
		
		if (num1 != num2) 
			return Math.max(num1, num2);
		else 
			if (num1 == Integer.MAX_VALUE) 
				return Integer.MAX_VALUE;
			else
				return num1 + 1;
	}
	
	public Object visit(MathBinaryOp binaryOp) throws SemanticError 
	{
		int first= (Integer)binaryOp.getFirstOperand().accept(this);
		int second=(Integer) binaryOp.getSecondOperand().accept(this);
		
		int numOfReg = settiUlmanAlg(first,second);
		binaryOp.setNumOfRegisters(numOfReg);
		return numOfReg;
	}

	public Object visit(LogicalBinaryOp binaryOp) throws SemanticError {
		
		int first= (Integer)binaryOp.getFirstOperand().accept(this);
		int second=(Integer) binaryOp.getSecondOperand().accept(this);
		
		int numOfReg = settiUlmanAlg(first,second);
		binaryOp.setNumOfRegisters(numOfReg);
		return numOfReg;
	}
	public Object visit(ArrayLocation location) throws SemanticError 
	{
		int first= (Integer)location.getArray().accept(this);
		int second=(Integer) location.getIndex().accept(this);
		
		int numOfReg = settiUlmanAlg(first,second);
		location.setNumOfRegisters(numOfReg);
		return numOfReg;
	}

	public Object visit(Assignment assignment) throws SemanticError {
		
		int first= (Integer)assignment.getAssignment().accept(this);
		int second=(Integer) assignment.getVariable().accept(this);
		
		int numOfReg = settiUlmanAlg(first,second);
		assignment.setNumOfRegisters(numOfReg);
		return numOfReg;
	}
	
	//******************************************
	
	
}
